﻿//RealTimeCandleBuilderSource.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Candles.Compression
{
	using System;
	using System.Collections.Generic;
	using System.Linq;

	using Ecng.Collections;
	using Ecng.ComponentModel;

	using StockSharp.BusinessEntities;

	/// <summary>
	/// Базовый источник данных для <see cref="ICandleBuilder"/>, который получает данные из <see cref="ITrader"/>.
	/// </summary>
	/// <typeparam name="T">Тип исходных данных (например, <see cref="Trade"/>).</typeparam>
	public abstract class RealTimeCandleBuilderSource<T> : ConvertableCandleBuilderSource<T>
	{
		private readonly SynchronizedDictionary<Security, CachedSynchronizedList<CandleSeries>> _registeredSeries = new SynchronizedDictionary<Security, CachedSynchronizedList<CandleSeries>>();

		/// <summary>
		/// Создать <see cref="RealTimeCandleBuilderSource{T}"/>.
		/// </summary>
		/// <param name="trader">Торговый шлюз, через который будут получаться новые данные.</param>
		protected RealTimeCandleBuilderSource(ITrader trader)
		{
			if (trader == null)
				throw new ArgumentNullException("trader");

			Trader = trader;
		}

		/// <summary>
		/// Приоритет источника по скорости (0 - самый оптимальный).
		/// </summary>
		public override int SpeedPriority
		{
			get { return 1; }
		}

		/// <summary>
		/// Торговый шлюз, через который будут получаться новые данные.
		/// </summary>
		public ITrader Trader { get; private set; }

		/// <summary>
		/// Запросить получение данных.
		/// </summary>
		/// <param name="series">Серия свечек, для которой необходимо начать получать данные.</param>
		/// <param name="from">Начальная дата, с которой необходимо получать данные.</param>
		/// <param name="to">Конечная дата, до которой необходимо получать данные.</param>
		public override void Start(CandleSeries series, DateTime from, DateTime to)
		{
			if (series == null)
				throw new ArgumentNullException("series");

			bool registerSecurity;

			series.IsNew = true;
			_registeredSeries.SafeAdd(series.Security, out registerSecurity).Add(series);

			if (registerSecurity)
				RegisterSecurity(series.Security);
		}

		/// <summary>
		/// Прекратить получение данных, запущенное через <see cref="Start"/>.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		public override void Stop(CandleSeries series)
		{
			if (series == null)
				throw new ArgumentNullException("series");

			var registeredSeries = _registeredSeries.TryGetValue(series.Security);

			if (registeredSeries == null)
				return;

			registeredSeries.Remove(series);

			if (registeredSeries.Count == 0)
			{
				UnRegisterSecurity(series.Security);
				_registeredSeries.Remove(series.Security);
			}

			RaiseStopped(series);
		}

		/// <summary>
		/// Зарегистрировать получение данных для инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		protected abstract void RegisterSecurity(Security security);

		/// <summary>
		/// Остановить получение данных для инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		protected abstract void UnRegisterSecurity(Security security);

		/// <summary>
		/// Получить ранее накопленные значения.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <returns>Накопленные значения.</returns>
		protected abstract IEnumerable<T> GetSecurityValues(Security security);

		/// <summary>
		/// Добавить синхронно новые данные, полученные от <see cref="Trader"/>.
		/// </summary>
		/// <param name="values">Новые данные.</param>
		protected void AddNewValues(IEnumerable<T> values)
		{
			if (_registeredSeries.Count == 0)
				return;

			foreach (var group in Convert(values).GroupBy(v => v.Security))
			{
				var security = group.Key;

				var registeredSeries = _registeredSeries.TryGetValue(security);

				if (registeredSeries == null)
					continue;

				var seriesCache = registeredSeries.Cache;

				var securityValues = group.OrderBy(v => v.Id).ToArray();

				foreach (var series in seriesCache)
				{
					if (series.IsNew)
					{
						RaiseProcessing(series, Convert(GetSecurityValues(security)).OrderBy(v => v.Id));
						series.IsNew = false;
					}
					else
					{
						RaiseProcessing(series, securityValues);
					}
				}
			}
		}
	}

	/// <summary>
	/// Источник данных для <see cref="CandleBuilder{TCandle}"/>, который создает <see cref="ICandleBuilderSourceValue"/> из тиковых сделок <see cref="Trade"/>.
	/// </summary>
	public class TradeCandleBuilderSource : RealTimeCandleBuilderSource<Trade>
	{
		/// <summary>
		/// Создать <see cref="TradeCandleBuilderSource"/>.
		/// </summary>
		/// <param name="trader">Торговый шлюз, через который будут получаться новые сделки, используя событие <see cref="ITrader.NewTrades"/>.</param>
		public TradeCandleBuilderSource(ITrader trader)
			: base(trader)
		{
			Trader.NewTrades += AddNewValues;
		}

		/// <summary>
		/// Получить временные диапазоны, для которых у данного источниках для передаваемой серии свечек есть данные.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <returns>Временные диапазоны.</returns>
		public override IEnumerable<Range<DateTime>> GetSupportedRanges(CandleSeries series)
		{
			if (series == null)
				throw new ArgumentNullException("series");

			var trades = GetSecurityValues(series.Security);

			yield return new Range<DateTime>(trades.IsEmpty() ? series.GetMarketTime() : trades.Min(v => v.Time), DateTime.MaxValue);
		}

		/// <summary>
		/// Зарегистрировать получение данных для инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		protected override void RegisterSecurity(Security security)
		{
			Trader.RegisterTrades(security);
		}

		/// <summary>
		/// Остановить получение данных для инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		protected override void UnRegisterSecurity(Security security)
		{
			Trader.UnRegisterTrades(security);
		}

		/// <summary>
		/// Получить ранее накопленные значения.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <returns>Накопленные значения.</returns>
		protected override IEnumerable<Trade> GetSecurityValues(Security security)
		{
			return Trader.Trades.Filter(security);
		}

		/// <summary>
		/// Освободить занятые ресурсы.
		/// </summary>
		protected override void DisposeManaged()
		{
			Trader.NewTrades -= AddNewValues;
			base.DisposeManaged();
		}
	}

	/// <summary>
	/// Источник данных для <see cref="CandleBuilder{TCandle}"/>, который создает <see cref="ICandleBuilderSourceValue"/> из стакана <see cref="MarketDepth"/>.
	/// </summary>
	public class MarketDepthCandleBuilderSource : RealTimeCandleBuilderSource<MarketDepth>
	{
		/// <summary>
		/// Создать <see cref="MarketDepthCandleBuilderSource"/>.
		/// </summary>
		/// <param name="trader">Торговый шлюз, через который будут получаться измененные стаканы, используя событие <see cref="ITrader.MarketDepthsChanged"/>.</param>
		public MarketDepthCandleBuilderSource(ITrader trader)
			: base(trader)
		{
			Trader.MarketDepthsChanged += OnMarketDepthsChanged;
		}

		/// <summary>
		/// Получить временные диапазоны, для которых у данного источниках для передаваемой серии свечек есть данные.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <returns>Временные диапазоны.</returns>
		public override IEnumerable<Range<DateTime>> GetSupportedRanges(CandleSeries series)
		{
			if (series == null)
				throw new ArgumentNullException("series");

			yield return new Range<DateTime>(series.GetMarketTime(), DateTime.MaxValue);
		}

		/// <summary>
		/// Зарегистрировать получение данных для инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		protected override void RegisterSecurity(Security security)
		{
			Trader.RegisterMarketDepth(security);
		}

		/// <summary>
		/// Остановить получение данных для инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		protected override void UnRegisterSecurity(Security security)
		{
			Trader.UnRegisterMarketDepth(security);
		}

		/// <summary>
		/// Получить ранее накопленные значения.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <returns>Накопленные значения.</returns>
		protected override IEnumerable<MarketDepth> GetSecurityValues(Security security)
		{
			return Enumerable.Empty<MarketDepth>();
		}

		private void OnMarketDepthsChanged(IEnumerable<MarketDepth> depths)
		{
			AddNewValues(depths.Select(d => d.Clone()));
		}

		/// <summary>
		/// Освободить занятые ресурсы.
		/// </summary>
		protected override void DisposeManaged()
		{
			Trader.MarketDepthsChanged -= OnMarketDepthsChanged;
			base.DisposeManaged();
		}
	}
}